﻿using Glue.Converters;
using Glue.Internals.Nulls;

namespace Glue.Internals
{
    internal partial class RelationFactory
    {
        private readonly ConverterContainer converterContainer;
        private readonly RelationDirectionSolver directionSolver = new RelationDirectionSolver();

        /// <summary>
        /// Only to be instanciated by Mapping
        /// </summary>
        public RelationFactory(ConverterContainer converterContainer)
        {
            this.converterContainer = converterContainer;
        }

        /// <summary>
        /// Will try to create a valid Relation. If it fails it will throw an exception
        /// </summary>
        public IRelation Create(IObjectMember leftProperty, IObjectMember rightProperty, RelationDirection direction)
        {
            AssertConvertersExist(leftProperty, rightProperty);
            direction = GetValidatedDirection(leftProperty, rightProperty, direction);
            var converter = converterContainer.Get(leftProperty, rightProperty, direction);

            return new Relation(leftProperty, rightProperty, converter, direction);
        }

        private void AssertConvertersExist(IObjectMember type1, IObjectMember type2)
        {
            if (!converterContainer.CanConvert(type1, type2))
                throw new GlueException("Can't convert between properties '" + type1.Name +
                                            "' and '" + type2.Name + "'. Add a converter to allow this " +
                                            "(between types '" + type1.MemberType + "' and '" +
                                            type2.MemberType + "')");
        }

        /// <summary>
        /// Will try to create a valid Relation. If it fails it will throw an exception
        /// </summary>
        public IRelation Create(IObjectMember leftProperty, IObjectMember rightProperty, RelationDirection direction, IConverter converter)
        {
            direction = GetValidatedDirection(leftProperty, rightProperty, direction);

            converter = converterContainer.ValidateConverter(leftProperty, rightProperty, converter, direction);
            return new Relation(leftProperty, rightProperty, converter, direction);
        }

        private RelationDirection GetValidatedDirection(IObjectMember leftProperty, IObjectMember rightProperty, RelationDirection direction)
        {
            var validDirections = directionSolver.GetDirection(leftProperty, rightProperty);
            AssertDirectionIsValid(leftProperty, rightProperty, validDirections, direction);
            return direction;
        }

        public Maybe<IRelation> TryCreate(IObjectMember leftProperty, IObjectMember rightProperty, bool isLeftPropertyIgnored, bool isRightPropertyIgnored)
        {
            if (!converterContainer.CanConvert(leftProperty, rightProperty)) 
                return Maybe<IRelation>.Nothing();
            
            var adjustedDirection = directionSolver.GetDirection(leftProperty, rightProperty, isLeftPropertyIgnored, isRightPropertyIgnored);
            if (adjustedDirection == RelationDirection.None)
                return Maybe<IRelation>.Nothing();

            return new Maybe<IRelation>(new Relation(leftProperty, rightProperty, 
                converterContainer.Get(leftProperty, rightProperty, adjustedDirection), adjustedDirection));
        }

        private void AssertDirectionIsValid(IObjectMember leftProperty, IObjectMember rightProperty, RelationDirection adjustedDirection, RelationDirection direction)
        {
            if (adjustedDirection != RelationDirection.TwoWays && adjustedDirection != direction)
                throw new GlueException("The relation between '" + leftProperty.Name + "' and '" + rightProperty.Name + "' has atleast one readonly member, and can not be related " + direction);
        }
    }
}
